
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;

// 匹配开始标签 <div:xxx || <div
const startTagOpenReg = new RegExp(`^<${qnameCapture}`);
// 匹配结束标签 </div>
const endTagReg = new RegExp(`^<\\/${qnameCapture}[^>]*>`);
// 匹配标签属性 a=b || a="b" || a='b'
const attributeReg = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
// 匹配闭合标签 /> || >
const startTagCloseReg = /^\s*(\/?)>/;
// 匹配HTML注释 <!--
const commentReg = /^<!--/;
// 匹配注释结束 -->
const commentEndReg = /^-->/;

// html 最开始肯定是一个 < 号 vue2中的模板必须是一个标签开始
// 每次解析完一个内容则删除一个内容
export function parseHTML(html) {

    /*
        转换成抽象语法树
        {
            tag: '', // 标签名
            type: null, // 标签类型
            attrs: [], // 属性列表
            parent: null, // 父节点
            children: [], // 子节点
        }
        匹配到开始标签时，创建一个 ast 节点，并且将该节点入栈
        匹配到结束标签时，将栈中的最后一个节点弹出
    */

    const ELMENT_TYPE = 1; // 标签类型
    const TEXT_TYPE = 3; // 文本类型
    const COMMENT_TYPE = 8; // 注释类型
    const stack = []; // 栈存储节点
    let currentParent // 指向栈中的最后一个节点
    let root; // 根节点

    function createASTElement(tag, attrs) {
        return {
            tag,
            type: ELMENT_TYPE,
            attrs,
            parent: null,
            children: [],
        }
    }

    // 获取起始标签及属性
    function startTag(tag, attrs) {
        // 创造一个 ast 节点
        let node = createASTElement(tag, attrs);

        // 判断根节点是否为空
        if (!root) {
            // 为空则设置当前节点为树的根节点
            root = node;
        }

        // 若当前的 currentParent 有值
        if (currentParent) {
            // 则将当前节点的 parent 设置为 currentParent
            node.parent = currentParent;
            // 将当前节点添加到 currentParent 的 children 中
            currentParent.children.push(node);
        }

        // 将当前节点放入栈中
        stack.push(node);

        // 将栈中的最后一个节点指向当前节点
        currentParent = node;
    }

    // 获取文本内容
    function character(text) {
        currentParent.children.push({
            text,
            type: TEXT_TYPE,
            parent: currentParent,
        })
    }

    // 获取结束标签
    function endTag(tag) {
        stack.pop();
        currentParent = stack.at(-1);
    }

    // 获取注释文本
    function commentText(comment) {
        currentParent.children.push({
            comment,
            type: COMMENT_TYPE,
            parent: currentParent,
        })
    }

    // 删除解析后的标签及属性内容
    function advance(n) {
        html = html.substring(n);
    }

    // 解析开始标签及属性
    function parseStartTag() {
        // 匹配是否是开始标签
        const start = html.match(startTagOpenReg);

        if (start) {
            const match = {
                tagName: start[1],
                attrs: [],
            }
            // 删除匹配到的开始标签
            advance(start[0].length);

            // 匹配不是闭合符合但是属性的
            let end, attr;
            while (!(end = html.match(startTagCloseReg)) && (attr = html.match(attributeReg))) {
                // 删除匹配到的属性
                advance(attr[0].length);
                // 将属性添加到数组中
                match.attrs.push({
                    key: attr[1],
                    value: attr[3] || attr[4] || attr[5] || true,
                });
            }

            if (end) {
                // 删除匹配到的闭合符号
                advance(end[0].length);
            }

            return match;
        }

        // 不是开始标签
        return false;
    }

    // 循环解析模板
    while (html) {
        // 如果 indexOf 中的索引是 0 则说明是开始标签或结束标签
        // 如果 indexOf 中的索引不是 0  则说明是文本结束位置
        let textEnd = html.indexOf('<');
        // 解析标签与属性
        if (textEnd == 0) {
            // 解析开始标签与属性
            const startTagMatch = parseStartTag();
            if (startTagMatch) {
                startTag(startTagMatch.tagName, startTagMatch.attrs);
                continue;
            }

            // 解析结束标签
            let endTagMatch = html.match(endTagReg);
            if (endTagMatch) {
                endTag(endTagMatch[0]);
                advance(endTagMatch[0].length);
                continue;
            }

            // 解析 HTML 注释
            let commentMatch = html.match(commentReg);
            if (commentMatch) {
                // 删除注释 <!--
                advance(commentMatch[0].length);
                // 获取注释内容并删除
                const comment = html.substring(0, html.indexOf('-->'));
                commentText(comment)
                advance(comment.length);
                // 删除注释 --> 
                const end = html.match(commentEndReg);
                advance(end[0].length);
                continue;
            }
        }
        // 解析文本
        if (textEnd > 0) {
            let text = html.substring(0, textEnd);
            if (text) {
                // 过滤空文本
                if ((text = text.trim())) {
                    character(text);
                }
                advance(textEnd);
            }
        }
    }

    // 返回语法数树
    return root;
}